﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Web.UI;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Server control used to generate a jQuery UI Slider widget with a two values.
	/// </summary>
	public class SliderRange : SliderBase {


		#region Constants: Property defaults

		private const int defaultStartValue = 0;
		private const int defaultEndValue = 100;
		private const string defaultStartValueDisplayControl = "";
		private const string defaultEndValueDisplayControl = "";

		#endregion


		#region Static Methods: Private routine

		private static void RenderDisplayFieldInitializeScript(ScriptBuilder script, string clientFunction, string displayClientId, string displaySelectorSuffix, string clientId, int index) {
			script.RenderSelector(displayClientId, displaySelectorSuffix);
			script.Append(clientFunction);
			script.RenderSelector(clientId);
			script.Append(".slider(");
			script.AppendValue("values");
			script.Append(")[");
			script.AppendValue(index);
			script.Append("]);");
		}

		#endregion;


		#region Members

		private string startValueDisplayControlClientId;
		private string endValueDisplayControlClientId;
		private bool valueChangedRaised;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.SliderRange class.
		/// </summary>
		public SliderRange() { }

		#endregion


		#region Properties: Widget initialization options

		/// <summary>
		/// Determines the first value of the slider.
		/// This property maps along with EndValue to the "values" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="EndValue"/>
		[DefaultValue(defaultStartValue), Category("Data")]
		public virtual int StartValue {
			get {
				EnsureChildControls();
				string value = StartValueField.Value;
				return string.IsNullOrEmpty(value) ? defaultStartValue : ParseStartValue(value);
			}
			set {
				EnsureChildControls();
				StartValueField.Value = value.ToString(CultureInfo.InvariantCulture);
			}
		}

		/// <summary>
		/// Determines the second value of the slider.
		/// This property maps along with StartValue to the "values" option when the jQuery UI widget is initialized.
		/// </summary>
		/// <seealso cref="StartValue"/>
		[DefaultValue(defaultEndValue), Category("Data")]
		public virtual int EndValue {
			get {
				EnsureChildControls();
				string value = EndValueField.Value;
				return string.IsNullOrEmpty(value) ? defaultEndValue : ParseEndValue(value);
			}
			set {
				EnsureChildControls();
				EndValueField.Value = value.ToString(CultureInfo.InvariantCulture);
			}
		}

		#endregion


		#region Properties: Extended widget functionality

		/// <summary>
		/// Gets or sets reference to a control that can display the first value as it's being manipulated on the client.
		/// </summary>
		[DefaultValue(defaultStartValueDisplayControl), Category("Appearance"), IDReferenceProperty]
		public virtual string StartValueDisplayControl {
			get { return ViewState["StartValueDisplayControl"] as string ?? defaultStartValueDisplayControl; }
			set { ViewState["StartValueDisplayControl"] = value; }
		}

		/// <summary>
		/// Gets or sets reference to a control that can display the second value as it's being manipulated on the client.
		/// </summary>
		[DefaultValue(defaultEndValueDisplayControl), Category("Appearance"), IDReferenceProperty]
		public virtual string EndValueDisplayControl {
			get { return ViewState["EndValueDisplayControl"] as string ?? defaultEndValueDisplayControl; }
			set { ViewState["EndValueDisplayControl"] = value; }
		}

		#endregion


		#region Properties: Sub Control

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the StartValue property.
		/// </summary>
		protected virtual PostBackField StartValueField { get; set; }

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the EndValue property.
		/// </summary>
		protected virtual PostBackField EndValueField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the StartValue property.
		/// </summary>
		public virtual string StartValueClientId {
			get { return StartValueField.ClientID; }
		}

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the EndValue property.
		/// </summary>
		public virtual string EndValueClientId {
			get { return EndValueField.ClientID; }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the StartValue property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler StartValueChanged;

		/// <summary>
		/// Occurs on postback when the EndValue property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler EndValueChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the ValueChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected override void OnValueChanged(EventArgs e) {
			if(valueChangedRaised) return;
			base.OnValueChanged(e);
			valueChangedRaised = true;
		}

		/// <summary>
		/// Raises the StartValueChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnStartValueChanged(EventArgs e) {
			if(StartValueChanged != null) StartValueChanged.Invoke(this, e);
		}

		/// <summary>
		/// Raises the EndValueChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnEndValueChanged(EventArgs e) {
			if(EndValueChanged != null) EndValueChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparatin for posting back or rendering.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void CreateChildControls() {
			base.CreateChildControls();

			StartValueField = new PostBackField();
			StartValueField.ID = ID + ClientIDSeparator + "StartValue";
			StartValueField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(StartValueField_ValueChanged);
			Controls.Add(StartValueField);

			EndValueField = new PostBackField();
			EndValueField.ID = ID + ClientIDSeparator + "EndValue";
			EndValueField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(EndValueField_ValueChanged);
			Controls.Add(EndValueField);
		}

		/// <summary>
		/// Raises the System.Web.UI.Control.PreRender event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected override void OnPreRender(EventArgs e) {	
			base.OnPreRender(e);

			startValueDisplayControlClientId = this.GetClientId(StartValueDisplayControl);
			endValueDisplayControlClientId = this.GetClientId(EndValueDisplayControl);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {
			base.RenderLoadScript(script);

			script.NewLine();

			if(!string.IsNullOrEmpty(startValueDisplayControlClientId))
				RenderDisplayControlInitScript(script, startValueDisplayControlClientId, 0);

			script.NewLine();

			if(!string.IsNullOrEmpty(endValueDisplayControlClientId))
				RenderDisplayControlInitScript(script, endValueDisplayControlClientId, 1);
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected override PropertyCollection CreateProperties() {
			PropertyCollection properties = base.CreateProperties();

			//Always add range for this server control
			properties.AddValue("range", true);

			//Always render values, since true default is null
			properties.AddValue("values", new int[] {StartValue, EndValue});

			ScriptBuilder slideScript = new ScriptBuilder();
			RenderSlideScript(slideScript);
			if(slideScript.Length != 0) properties.AddScript("slide", slideScript);

			return properties;
		}

		/// <summary>
		/// Renders the client "slide" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderSlideScript(ScriptBuilder script) {

			script.Append("function(event,ui){ ");

			script.RenderSelector(StartValueClientId);
			script.Append(".val(ui.values[0]); ");

			script.RenderSelector(EndValueClientId);
			script.Append(".val(ui.values[1]); ");

			if(!string.IsNullOrEmpty(startValueDisplayControlClientId)) {
				script.RenderSelector(startValueDisplayControlClientId, ":input");
				script.Append(".val(ui.values[0]); ");
				script.RenderSelector(startValueDisplayControlClientId, ":not(:input)");
				script.Append(".text(ui.values[0]); ");
			}

			if(!string.IsNullOrEmpty(endValueDisplayControlClientId)) {
				script.RenderSelector(endValueDisplayControlClientId, ":input");
				script.Append(".val(ui.values[1]); ");
				script.RenderSelector(endValueDisplayControlClientId, ":not(:input)");
				script.Append(".text(ui.values[1]); ");
			}

			script.Append("}");

		}

		/// <summary>
		/// Renders the script for initially sychronizing the value of the StartValueDisplayControl and EndValueDisplayControl
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		/// <param name="displayClientId">The client ID of the specific control being referenced.</param>
		/// <param name="index">The value index of the specific control being referenced.</param>
		protected virtual void RenderDisplayControlInitScript(ScriptBuilder script, string displayClientId, int index) {

			string clientId = ClientID;

			RenderDisplayFieldInitializeScript(script, ".val(", displayClientId, ":input", clientId, index);
			script.NewLine();
			RenderDisplayFieldInitializeScript(script, ".text(", displayClientId, ":not(:input)", clientId, index);
		}

		#endregion


		#region Methods: Private routines

		private int ParseStartValue(string text) {
			int value;
			if(int.TryParse(text, out value)) return value;
			return Min;
		}

		private int ParseEndValue(string text) {
			int value;
			if(int.TryParse(text, out value)) return value;
			return Max;
		}

		private int ConstrainStartValue(int newValue) {
			return Math.Max(Min, Math.Min(EndValue, Math.Min(Max, newValue)));
		}

		private int ConstrainEndValue(int newValue) {
			return Math.Max(Min, Math.Max(StartValue, Math.Min(Max, newValue)));
		}

		#endregion


		#region Event handlers

		private void StartValueField_ValueChanged(object sender, PostBackFieldEventArgs e) {

			int oldValue = ParseStartValue(e.OldValue);
			int newValue = ParseStartValue(e.NewValue);

			newValue = ConstrainStartValue(newValue);

			if(newValue == oldValue) return;

			OnValueChanged(EventArgs.Empty);
			OnStartValueChanged(EventArgs.Empty);
		}

		private void EndValueField_ValueChanged(object sender, PostBackFieldEventArgs e) {

			int oldValue = ParseEndValue(e.OldValue);
			int newValue = ParseEndValue(e.NewValue);

			newValue = ConstrainEndValue(newValue);

			if(newValue == oldValue) return;

			OnValueChanged(EventArgs.Empty);
			OnEndValueChanged(EventArgs.Empty);
		}

		#endregion


	}

}